#include <QQmlFile>
#include <QFile>
#include <private/qssgmesh_p.h>
#include "minecraftalgorthm.h"

static void pushVertix(const QVector3D& ver,const QVector2D& uv,QByteArray& target){
    float vertice_values[3] = {ver.x(),ver.y(),ver.z()};
    target.append((const char*)vertice_values,sizeof(vertice_values));

    float uv_values[2] = {uv.x(),uv.y()};
    target.append((const char*)uv_values,sizeof(uv_values));
}

static void genTraingle(QVector3D* verArr,QVector2D* uv,QByteArray & target){
    pushVertix(verArr[0],uv[0],target);
    pushVertix(verArr[1],uv[1],target);
    pushVertix(verArr[2],uv[2],target);
}

static void genPlain(QVector3D* verArr,const QRectF & uv,QByteArray & target){
    QVector3D f1[3] = {verArr[0],verArr[3],verArr[2]};
    QVector2D u1[3] = {QVector2D(uv.x(),uv.y()),QVector2D(uv.x(),uv.y() + uv.height()),QVector2D(uv.x() + uv.width(),uv.y() + uv.height())};
    genTraingle(f1,u1,target);

    QVector3D f2[3] = {verArr[0],verArr[2],verArr[1]};
    QVector2D u2[3] = {QVector2D(uv.x(),uv.y()),QVector2D(uv.x() + uv.width(),uv.y() + uv.height()),QVector2D(uv.x() + uv.width(),uv.y())};
    genTraingle(f2,u2,target);
}

static QRectF genUV(const QVector2D& v){
    const static QSizeF texSize = {1024,1024};//原材质贴图尺寸
    const static QSizeF blockSize = {16,16}; //贴图中一个方块占据的大小
    const static QSizeF frameSize = {18,18}; //贴图中一个方块的边框占据的大小
    const static QSizeF blockSizeF = {blockSize.width() / texSize.width(),blockSize.height() / texSize.height()};
    double x,y,width,height;

    x = (v.x() * frameSize.width() + 1) / texSize.width();
    y = (v.y() * frameSize.height() + 1) / texSize.height();
    width = blockSizeF.width();
    height = blockSizeF.height();

    y = 1-y - height;

    return {x,y,width,height};
}

void MineCraftAlgorthm::genMineCraftBlock(QByteArray &out, const QVector3D &centerLocation, const QList<QVector2D>& faces,Direction direction)
{
    QVector3D size = {100,100,100};
    QVector3D vertex[8];//方盒子对应的8点
    vertex[0] = {size[0] / 2,size[1] / 2,size[2] / 2};
    vertex[1] = {size[0] / 2,size[1] / 2,-size[2] / 2};
    vertex[2] = {size[0] / 2,-size[1] / 2,size[2] / 2};
    vertex[3] = {size[0] / 2,-size[1] / 2,-size[2] / 2};
    vertex[4] = {-size[0] / 2,size[1] / 2,size[2] / 2};
    vertex[5] = {-size[0] / 2,size[1] / 2,-size[2] / 2};
    vertex[6] = {-size[0] / 2,-size[1] / 2,size[2] / 2};
    vertex[7] = {-size[0] / 2,-size[1] / 2,-size[2] / 2};

    for(auto& i:vertex)
    {
        i += centerLocation;
    }

    auto& lastTexPoint = faces.back();
    if(direction & Left)
    {
        QVector3D plain[] = {vertex[6],vertex[7],vertex[5],vertex[4],};
        auto uv = genUV(faces.value(0,lastTexPoint));
        genPlain(plain,uv,out);
    }

    if(direction & Right)
    {
        QVector3D plain[] = {vertex[3],vertex[2],vertex[0],vertex[1],};
        auto uv = genUV(faces.value(1,lastTexPoint));
        genPlain(plain,uv,out);
    }

    if(direction & Up)
    {
        QVector3D plain[] = {vertex[0],vertex[4],vertex[5],vertex[1]};
        auto uv = genUV(faces.value(2,lastTexPoint));
        genPlain(plain,uv,out);
    }

    if(direction & Down)
    {
        QVector3D plain[] = {vertex[2],vertex[3],vertex[7],vertex[6]};
        auto uv = genUV(faces.value(3,lastTexPoint));
        genPlain(plain,uv,out);
    }

    if(direction & Front)
    {
        QVector3D plain[] = {vertex[2],vertex[6],vertex[4],vertex[0]};
        auto uv = genUV(faces.value(4,lastTexPoint));
        genPlain(plain,uv,out);
    }

    if(direction & Behind)
    {
        QVector3D plain[] = {vertex[7],vertex[3],vertex[1],vertex[5]};
        auto uv = genUV(faces.value(5,lastTexPoint));
        genPlain(plain,uv,out);
    }
}

class MeshManager{

    struct MeshData{//将mesh数据优化成仅有位置和贴图信息
        QByteArray mesh;
        int pos_offset;
        int stride;
    };
public:
    static MeshManager* instance(){
        static MeshManager ret;

        return &ret;
    }

    QByteArray createMesh(const QUrl& mesh, const QMatrix4x4 &mtx){
        QByteArray ret;
        auto filePath = QQmlFile::urlToLocalFileOrQrc(mesh);
        MeshData* data = nullptr;
        if(!m_cache.contains(filePath))
        {
            m_cache[filePath] = loadMesh(filePath);
        }

        data = &m_cache[filePath];

        ret = transform(data,mtx);

        return ret;
    }

    MeshData loadMesh(const QString& mesh){
        QFile file(mesh);
        file.open(QIODevice::ReadOnly);
        auto meshData = QSSGMesh::Mesh::loadMesh(&file);
        auto verBuff = meshData.vertexBuffer();
        auto indexBuff = meshData.indexBuffer();
        auto indexSize = QSSGBaseTypeHelpers::getSizeOfType(indexBuff.componentType);
        int count = indexBuff.data.size() / indexSize;
        MeshData ret;
        ret.stride = sizeof(float) * (3 + 2);


        ret.mesh.resize(count * ret.stride);
        ret.pos_offset = 0;
        auto dstBegin = ret.mesh.data();
        auto dstEnd = ret.mesh.data() + ret.mesh.size();
        auto srcBegin = verBuff.data.data();
        auto srcEnd = verBuff.data.data() + verBuff.data.size();

        int posOffset,uvOffset,normOffset = 0;
        int dstPosOffset = 0,dstUvOffset = 3 * sizeof(float);
        for(const auto&e:verBuff.entries)
        {
            if(e.name == "attr_pos")
            {
                posOffset = e.offset;
            }
            else if(e.name == "attr_uv0")
            {
                uvOffset = e.offset;
            }
        }
        int i = 0;
        char* dst = dstBegin,*indexSrc = indexBuff.data.data(),*verSrc = verBuff.data.data();
        for(;i<count;i++,dst += ret.stride,indexSrc += indexSize)
        {
            int index = *(int*)indexSrc;
            auto vertix = verSrc + (verBuff.stride) * index;

            memcpy(dst + dstPosOffset,vertix + posOffset,3 * sizeof(float));
            memcpy(dst + dstUvOffset,vertix + uvOffset,2 * sizeof(float));
        }
        return ret;
    }

    QByteArray transform(MeshData* mesh,const QMatrix4x4 &mtx){
        QByteArray ret;
        ret = mesh->mesh;
        auto begin = ret.data();
        auto size = ret.size();
        auto end = size+begin;

        for(auto stride = begin;stride < end;stride += mesh->stride)
        {
            QVector3D* vec = (QVector3D*)(stride + mesh->pos_offset);
            *vec = mtx.map(*vec);
        }

        return ret;
    }

    QMap<QString,MeshData> m_cache;
};

void MineCraftAlgorthm::createMeshBlock(QByteArray &out, const QUrl &mesh, const QMatrix4x4 &mtx)
{
    out.append(MeshManager::instance()->createMesh(mesh,mtx));
}

